// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <cmath>
#include <utility>
#include <algorithm>
#include <vector>
#include <memory>
#include "fastfeature/simulator_spec.pb.h"
#include "fastfeature/feature_feed.pb.h"

#include "presto/quant/feature/util/decimal.h"
#include "coin2/exchange/sim/order_v1/light_book.h"

namespace coin2::exchange::sim::order_v1 {

using ::presto::quant::feature::OrderStateProto;
using ::presto::Symbol;

using PriceType = dec::decimal<12>;
typedef LightBook<10, PriceType> LightBook_10_dec12;

struct QueueStatus;

struct TakerFillInfo {
  bool ready = false;
  double fill_pq = 0;
  double fill_qty = 0;
  double fill_price() const {
    if (fill_qty > 0) {
      return fill_pq / fill_qty;
    }
    return 0;
  }
};

struct BatchTradeContainer {
  BatchTradeContainer(
      int64_t prev_batch_time,
      int64_t order_arrival_time,
      int64_t batch_until_ts)
      : t0(prev_batch_time),
        order_arrival_time(order_arrival_time),
        batch_until_ts(batch_until_ts) {}

  bool WithinBatch(int64_t fetched_time) const {
    return batch_until_ts >= fetched_time;
  }

  void AddBatch(const ::fastfeature::FeedMessage& feed) {
    // don't like feed proto copy, but it's trade, light enough anyway.
    tn = feed.fetched_time();
    trades.push_back(feed);
  }

  void MatchAndFlushBatch(QueueStatus* qs);

  int64_t t0;
  int64_t tn;
  int64_t order_arrival_time;
  int64_t batch_until_ts;
  std::vector<::fastfeature::FeedMessage> trades;
};

struct QueueStatus {
  QueueStatus() = default;
  QueueStatus(int64_t time,
              int64_t order_id,
              int sign,
              bool post_only,
              std::string tag,
              bool ioc,
              double makerfill_tolerance_bps,
              const PriceType& price, double order_qty, double remain_qty, double queue_pos,
              double bid0_price_on_accepted, double ask0_price_on_accepted,
              const Symbol* last_feed_symbol, ::fastfeature::FeedType last_feed_type)
      : accepted(false),
        is_snapshot(false),
        time(time),
        order_id(order_id),
        sign(sign),
        post_only(post_only),
        tag(tag),
        ioc(ioc),
        makerfill_tolerance_bps(makerfill_tolerance_bps),
        price(price),
        order_qty(order_qty),
        remain_qty(remain_qty),
        queue_pos(queue_pos),
        queue_pos_ratio(1),
        queue_pos_init(queue_pos),
        bid0_price_on_accepted(0),
        ask0_price_on_accepted(0),
        last_feed_symbol(last_feed_symbol),
        last_feed_type(last_feed_type),
        bid0_price_on_time(bid0_price_on_accepted),
        ask0_price_on_time(ask0_price_on_accepted),
        alive(true),
        accepted_time(0),
        cancel_arrival_time(0),
        cancel_confirmed_time(0) {
    CHECK(!(post_only && ioc)) << "post_only & ioc can't be both true";
  }

  // buy first, inner first, priority queue first
  bool operator<(const QueueStatus& rhs) const {
    if (sign != rhs.sign) {
      return sign < rhs.sign;
    }
    if (price != rhs.price) {
      if (sign == 1) {
        return price > rhs.price;
      } else if (sign == -1) {
        return price < rhs.price;
      }
    }
    if (order_id != rhs.order_id) {
      return order_id < rhs.order_id;
    }
    return queue_pos < rhs.queue_pos;
  }

  OrderStateProto GetOrderStateProto(int64_t timestamp) const {
    OrderStateProto ret;
    ret.set_order_id(order_id);
    ret.set_price(price.getAsDouble());
    ret.set_remain_qty(remain_qty);
    ret.set_sign(sign);
    ret.set_order_age(timestamp - time);
    ret.set_accepted_time(time);
    if (fill_qty_sum == 0) {
      ret.set_price_avg_fill(0);
      ret.set_qty_accum_fill(0);
    } else {
      ret.set_price_avg_fill(fill_pq_sum / fill_qty_sum);
      ret.set_qty_accum_fill(fill_qty_sum);
    }
    return ret;
  }

  static bool close_enough(double a, double b) {
    if (std::abs(a - b) <= 1e-8 * std::max<double>(
        std::abs(a), std::abs(b))) {
      return true;
    }
    return false;
  }

  void AddFill(double fill_price, double fill_qty) {
    if (close_enough(remain_qty, fill_qty)) {
      remain_qty = 0;
    } else {
      remain_qty -= fill_qty;
    }
    fill_pq_sum += fill_price * fill_qty;
    fill_qty_sum += fill_qty;
  }

  void SetTakerFill(
      int64_t last_book_time,
      const LightBook_10_dec12& lb,
      TakerFillInfo* taker_fill) {
    std::pair<double, double> pq{0, 0};
    // if (time - last_book_time < 1e9)
    {
      if (sign == 1) {
        pq = GetBuyAggFillAtPrice(lb, price, remain_qty);
      } else if (sign == -1) {
        pq = GetSellAggFillAtPrice(lb, price, remain_qty);
      } else {
        CHECK(false) << sign;
      }
    }
    taker_fill->ready = true;
    taker_fill->fill_pq = pq.first * pq.second;
    taker_fill->fill_qty = pq.second;
  }

  void ApplyCancel(int64_t fetched_time);
  void ApplyTrade(const ::fastfeature::FeedMessage& feed);
  void ApplyBook(const LightBook_10_dec12& book);

  void AcceptOrder(
      const LightBook_10_dec12* prebook,
      const LightBook_10_dec12& nextbook);
  bool accepted{};
  bool is_snapshot{};

  TakerFillInfo taker_fill{};

  int64_t time{};
  int64_t order_id{};
  int sign{};
  bool post_only{};
  std::string tag{};
  bool ioc{};
  double makerfill_tolerance_bps{};

  PriceType price{};
  double order_qty{};
  double remain_qty{};
  // DEFINITION of queue_pos : 0 = front of the queue
  double queue_pos{};
  double queue_pos_ratio{};

  double queue_pos_init{};

  double bid0_price_on_accepted{};
  double ask0_price_on_accepted{};

  const Symbol* last_feed_symbol{};
  ::fastfeature::FeedType last_feed_type;

  double bid0_price_on_time{};
  double ask0_price_on_time{};

  bool alive{};

  double fill_pq_sum{};
  double fill_qty_sum{};
  int64_t accepted_time{};
  int64_t cancel_arrival_time{};
  int64_t cancel_confirmed_time{};

  std::unique_ptr<BatchTradeContainer> batch_trade{};
};

struct QueueStatusComparator {
  bool operator()(
      const std::shared_ptr<QueueStatus>& lhs,
      const std::shared_ptr<QueueStatus>& rhs) const {
    return *lhs < *rhs;
  }
};

}  // namespace coin2::exchange::sim::order_v1
